﻿#include "stdafx.h"
#if _QG_USE_GL
#include "qg_ogl_dev.h"
#include "qg_ogl_dev_ext.h"
#include "qg_ogl_mtrl.h"
#include "qg_ogl_cnv.h"
#include "qg_trace.h"
#include "qg_math.h"

using namespace _ogl;

// 스크린삿
qgImage* GLDevice::ScreenCapture()
{
	return NULL;
}

//
bool GLDevice::_ActiveTexture(kint stage) throw()
{
	if (_caps.supp_multi_texture)
	{
		if (_vprn.m_tex_active != stage)
		{
			_vprn.m_tex_active = stage;
			QglActiveTexture(GL_TEXTURE0 + stage);
		}

		return true;
	}
	else
	{
		if (stage > 0)
			return false;
		else
		{
			_vprn.m_tex_active = 0;
			return true;
		}
	}
}

//
bool GLDevice::_CommitTexture(kint stage, qgTexture* tex) throw()
{
	if (!_ActiveTexture(stage))
		return false;

	// 와... GL은 이게 귀찮구먼... 타입마다 다르네.
	GLenum last = _vprn.m_tex_type[stage];

	if (tex)
	{
		GLTexture* gltex = (GLTexture*)tex;
		GLenum prsn = gltex->_gl_type;

		if (last != prsn)
		{
			if (last != 0)
				glDisable(last);

			_vprn.m_tex_type[stage] = prsn;
			glEnable(prsn);
		}

		glBindTexture(prsn, (GLuint)gltex->GetDescription());

		return true;
	}
	else
	{
		if (last != 0)
		{
			_vprn.m_tex_type[stage] = 0;
			glDisable(last);

			// 아래꺼 안해도 될것같은데...
			glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
			glBindTexture(GL_TEXTURE_2D, 0);
		}

		return false;
	}
}

//
qgTexture* GLDevice::_RoamImplTexture(const qgArprTexture& ap)
{
	GLTexture* tex;

	if (K_OFLAG(ap.flags, QGTEXF_OFF))
	{
		GLTextureFBO* off = kobj_create(GLTextureFBO);
		tex = off;

		if (!off->_InitProperty(ap.type, ap.name, ap.width, ap.height, ap.depth, ap.mips, ap.flags, ap.fmt, this) ||
			!off->_CreateTexture(NULL, this) ||
			!off->_CreateOff(this))
		{
			tex->Unload();
			tex = NULL;
		}
	}
	else
	{
		tex = kobj_create(GLTexture);

		if (!tex->_InitProperty(ap.type, ap.name, ap.width, ap.height, ap.depth, ap.mips, ap.flags, ap.fmt, this) ||
			!tex->_CreateTexture(NULL, this))
		{
			tex->Unload();
			tex = NULL;
		}
	}

	// 만드는 동안 스테이지 0을 썼기에 리셋
	kobj_unload(_vprn.m_tex[0]);
	_vprn.m_tex[0] = NULL;
	_vprn.bd_tex[0] = true;

	return tex;
}

//
qgTexture* GLDevice::_RoamImplTexture(const qgArprImage& ap)
{
	GLTexture* tex = kobj_create(GLTexture);

	if (!tex->_InitImage(ap.name, ap.img, 0, ap.flags, this) ||
		!tex->_CreateTexture(ap.img, this))
	{
		tex->Unload();
		tex = NULL;
	}

	// 만드는 동안 스테이지 0을 썼기에 리셋
	kobj_unload(_vprn.m_tex[0]);
	_vprn.m_tex[0] = NULL;
	_vprn.bd_tex[0] = true;

	return tex;
}

//
qgTexture* GLDevice::_RoamImplMotionTexture(const char* filename, kint flags)
{
	GLTextureMotion* tex = kobj_create(GLTextureMotion);

	if (!tex->_CreateMotion(filename, flags, this))
	{
		tex->Unload();
		tex = NULL;
	}

	// 만드는 동안 스테이지 0을 썼기에 리셋
	kobj_unload(_vprn.m_tex[0]);
	_vprn.m_tex[0] = NULL;
	_vprn.bd_tex[0] = true;

	return tex;
}

//
qgDepthBuffer* GLDevice::_RoamImplDepthBuffer(const tpl::Point& size)
{
	GLDepthBuffer* buf = kobj_create(GLDepthBuffer);

	if (!buf->_Create(size.width, size.height, this))
	{
		buf->Unload();
		buf = NULL;
	}

	// 만드는 동안 스테이지 0을 썼을 수도 있기에 리셋, 버퍼 타입이면 안씀. 오직 텍스쳐 타입 만
	kobj_unload(_vprn.m_tex[0]);
	_vprn.m_tex[0] = NULL;
	_vprn.bd_tex[0] = true;

	return buf;
}


//////////////////////////////////////////////////////////////////////////
// 텍스쳐
KIMPL_SUBCLASS(GLTexture, "GLTexture");

//
GLTexture::GLTexture() throw()
: _gl_fmt(GL_RGBA), _pixel_fmt(GL_BGRA_EXT), _pixel_type(GL_UNSIGNED_BYTE)
{
}

//
GLTexture::~GLTexture() throw()
{
	if (_node_desc)
	{
		GLuint n = (GLuint)_node_desc;
		glDeleteTextures(1, &n);
	}

	kobj_unload(_img_rsw);
	kobj_unload(_img_mip);
}

//
bool GLTexture::_InitImage(const char* name, qgImage* img, kint mips, kint flags, GLDevice* dev)
{
	kint width = img->GetWidth();
	kint height = img->GetHeight();
	kint depth = 1;
	qgColorFormat fmt = img->GetColorFormat();

	return _InitProperty(QGTEXTURE_2D, name, width, height, depth, mips, flags, fmt, dev);
}

//
bool GLTexture::_InitProperty(qgTextureType type, const char* name, kint width, kint height, kint depth, kint mips, kint flags, qgColorFormat fmt, GLDevice* dev)
{
	//
	_key = name ? k_strdup(name) : k_apsprintf("gl_texture_%Lu", k_nth());
	_textype = type;
	_flags = flags;
	_mips = mips;
	_fmt = fmt == QGCF_NONE ? QGCF32_RGBA : fmt;

	// GL 포맷 맞춤
	_gl_type = GLConvert::TextureType(_textype);
	_gl_fmt = GLConvert::DetectColorFormat(_fmt, _pixel_fmt, _pixel_type, _alpha);

	if (_gl_fmt == GL_NONE)
	{
		// 지원 안함
		return false;
	}

	// 플래그 검사
	if (K_OFLAG(flags, QGRESF_DYNAMIC))
	{
		if (K_OFLAG(flags, QGTEXF_OFF))
		{
			// 다이나믹 + FBO 금지
			return false;
		}

		if (!K_OFLAG(flags, QGTEXF_NOMIP))
		{
			// 다이나믹 -> 밉맵 금지
			K_YFLAG(_flags, QGTEXF_NOMIP, true);
		}

		_mips = 1;

		// OPENGL은 바로 매핑이 없으므로 이미지를 강제로 처리하게 함
		K_YFLAG(_flags, QGTEXF_IMAGE, true);
	}
	else
	{
		if (K_OFLAG(flags, QGTEXF_OFF))
		{
			if (!K_OFLAG(flags, QGTEXF_NOMIP))
			{
				// 오프 -> 밉맵 금지
				K_YFLAG(_flags, QGTEXF_NOMIP, true);
			}

			K_YFLAG(_insts, QGTEXI_OFF, true);
			_mips = 1;
		}
		else
		{
			if (K_OFLAG(flags, QGTEXF_NOMIP))
				_mips = 1;
			else
			{
				K_YFLAG(_insts, QGTEXI_AUTOMIP, true);
				_mips = 0;
			}
		}
	}

	// 최대 크기로 맞춤
	kint maxsize = dev->GetCaps().max_texture_dimension;

	if (width < maxsize)
		_width = width;
	else
	{
		_width = maxsize;
		K_YFLAG(_insts, QGTEXI_CLAMP, true);
	}

	if (height < maxsize)
		_height = height;
	else
	{
		_height = maxsize;
		K_YFLAG(_insts, QGTEXI_CLAMP, true);
	}

	if (depth < maxsize)
		_depth = depth;
	else
	{
		_depth = maxsize;
		K_YFLAG(_insts, QGTEXI_CLAMP, true);
	}

	// 정방 맞춤
	if (dev->GetCaps().supp_texture_npo2)
	{
		_awidth = _width;
		_aheight = _height;
		_adepth = _depth;
	}
	else
	{
		_awidth = _math::OptimizeTextureSize(_width, true, 0);
		_aheight = _math::OptimizeTextureSize(_height, true, 0);
		_adepth = _math::OptimizeTextureSize(_depth, true, 0);

		if (_awidth != _width || _aheight != _height /*|| _adepth != _depth*/)
			K_YFLAG(_insts, QGTEXI_ADJPOW2, true);
	}

	// 샘플러 초기화
	_splr = qgSplrDataEx(QGFLT_NONE, QGADR_NONE, QGADR_NONE, QGADR_NONE, QGCOP_NONE, tpl::ColorValues::Black, 0.0f, tpl::Vec2(-FLT_MAX, FLT_MAX));

	// 모션
	if (K_OFLAG(flags, QGTEXF_MOTION))
		K_YFLAG(_insts, QGTEXI_MOTION, true);

	// 밉맵 개수를 얻는 함수가 없네... 계산으로 만들어야함
	if (_mips == 0)
	{
		// ceil(log_2(max(width, height))) + 1
		//kint maxv = K_MAX(_awidth, _aheight);
		// 아 귀찮...
	}

	return true;
}

//
bool GLTexture::_CreateInternal(GLuint* ptex, GLDevice* dev)
{
	// 텍스쳐 핸들 만들고, 일단 자기 자신에다 넣는다.
	// 어짜피 진짜 핸들은 나중에 설정할 테니 믿고 넣자
	glGenTextures(1, ptex);
	_node_desc = (kuintptr)*ptex;

	dev->_CommitTexture(0, this);

	// 추가 설정부터 해주고
	if (K_OFLAG(_insts, QGTEXI_OFF))
	{
		// FBO용 추가 설정
		glTexParameteri(_gl_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(_gl_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameteri(_gl_type, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
	}

	if (K_OFLAG(_insts, QGTEXI_AUTOMIP))
	{
		// 자동 밉맵
		glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
		glTexParameteri(_gl_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(_gl_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	}
	else
	{
		glTexParameteri(_gl_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(_gl_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	}

	// 텍스쳐 만들기
	switch (K_CAST_ENUM(_textype))
	{
		case QGTEXTURE_1D:
			glTexImage1D(GL_TEXTURE_1D, 0, _gl_fmt, _awidth, 0, _pixel_fmt, _pixel_type, NULL);
			break;

		case QGTEXTURE_2D:
			glTexImage2D(GL_TEXTURE_2D, 0, _gl_fmt, _awidth, _aheight, 0, _pixel_fmt, _pixel_type, NULL);
			break;

		case QGTEXTURE_3D:	//!!!! DEPTH 크기!!!
#if 0
			// 아래 함수가 기본에 엄따! Qgl로 만들어야 할 듯.
			//glTexImage3D(GL_TEXTURE_3D, 0, _gl_fmt, _awidth, _aheight, _adepth, 0, _pixel_fmt, _pixel_type, NULL);
			break;
#else
			return false;
#endif

		case QGTEXTURE_CUBE:
			for (kint f = 0; f < 6; f++)
				glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + f, 0, _gl_fmt, _awidth, _aheight, 0, _pixel_fmt, _pixel_type, NULL);
			break;
	}

	return true;
}

//
bool GLTexture::_CreateTexture(qgImage* img, GLDevice* dev)
{
	GLuint tex;

	if (!img)
	{
		// 빈 텍스쳐 만들기
		if (!_CreateInternal(&tex, dev))
			return false;
	}
	else
	{
		// 이미지
		_img_rsw = kobj_new(qgImage, _awidth, _aheight, _fmt);
		K_OFLAG(_insts, QGTEXI_ADJPOW2 | QGTEXI_CLAMP)
			? img->CopyToScale(_img_rsw)
			: img->CopyTo(_img_rsw);

		// 텍스쳐 핸들
		if (!_CreateInternal(&tex, dev))
			return false;

		// 업로드
		_UploadTexture(true, 0, dev);

		// 보관할 필요가 없으면 이미지 지움
		if (!K_OFLAG(_flags, QGTEXF_IMAGE))
		{
			_img_rsw->Unload();
			_img_rsw = NULL;
		}
	}

	// 내부 함수에서 이미 설정했기 때문에 할필요없음
	//_node_desc = (kuintptr)tex;

	return true;
}

//
void GLTexture::_UploadTexture(bool isnew, kint mipno, GLDevice* dev)
{
	qgImage* img = mipno == 0 ? _img_rsw : _img_mip;
	k_return_if_fail(img);

#if 0
	// 포맷 검사 해보기
	if (isnew)
		_gl_fmt = GLConvert::DetectColorFormat(_fmt, _pixel_fmt, _pixel_type, _alpha);
	else
	{
		// 몇몇 텍스쳐는 형식이 바뀔 수도 있다. 이러면 안되지만
		bool b;
		GLConvert::DetectColorFormat(_fmt, _pixel_fmt, _pixel_type, b);
	}
#endif

	// 텍스쳐 놓고
	dev->_CommitTexture(0, this);

	// 텍스쳐 이미지 처리
	if (!isnew)
		glTexSubImage2D(GL_TEXTURE_2D, mipno, 0, 0, img->GetWidth(), img->GetHeight(), _pixel_fmt, _pixel_type, img->GetData());
	else
	{
		// 처음 만들 때 설정을 넣는데, 여기 없어도 되는 루틴이긴 하지 않음?
		if (mipno == 0)
		{
			if (K_OFLAG(_insts, QGTEXI_AUTOMIP))
			{
				// 자동 밉맵
				glHint(GL_GENERATE_MIPMAP_HINT_SGIS, GL_FASTEST);
				glTexParameteri(_gl_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(_gl_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
			}
			else
			{
				glTexParameteri(_gl_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				glTexParameteri(_gl_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			}
		}

		glTexImage2D(GL_TEXTURE_2D, mipno, _gl_fmt, img->GetWidth(), img->GetHeight(), 0, _pixel_fmt, _pixel_type, img->GetData());
	}

	// 밉맵 자동
	if (mipno == 0 && K_OFLAG(_insts, QGTEXI_AUTOMIP))
	{
#ifdef GL_SGIS_generate_mipmap
		glTexParameteri(_gl_type, GL_GENERATE_MIPMAP, GL_TRUE);
#endif
		QglGenerateMipmap(_gl_type);
	}
}

//
bool GLTexture::EnterMap(qgTextureMap* map, kint flag, kint mipno, qgTextureFace face)
{
	k_return_value_if_fail(map, false);
	k_return_value_if_fail(_lock_mip < 0, false);

	// 잠글 수 있는 포맷인가
	GLenum pxfmt, pxtype;
	if (!GLConvert::TestLockableFormat(_fmt, pxfmt, pxtype))
		return false;

	//
	qgImage* img = _img_rsw;

	// 밉맵 편집이면 자동 밉맵 끔
#ifdef GL_SGIS_generate_mipmap
	if (mipno > 0 && K_OFLAG(_insts, QGTEXI_AUTOMIP))
		glTexParameteri(_gl_type, GL_GENERATE_MIPMAP, GL_FALSE);
#endif

	// 이미지가 없으면 만들기 (오프텍스쳐도 포함)
	if (!img || K_OFLAG(_insts, QGTEXI_OFF))
	{
		if (!img)
		{
			if (mipno == 0)
				img = _img_rsw = kobj_new(qgImage, _awidth, _aheight, _fmt);
			else
			{
				int i = 0, w = _awidth, h = _aheight;

				do
				{
					if (w > 1)
						w >>= 1;

					if (h > 1)
						h >>= 1;

					i++;
				} while (i != mipno);

				img = _img_mip = kobj_new(qgImage, w, h, _fmt);
			}
		}

		// 디바이스에서 이미지 다운로드
		if (K_OFLAG(flag, QGENTF_READ) && (_gl_type == GL_TEXTURE_1D || _gl_type == GL_TEXTURE_2D))
		{
			GLDevice* dev = (GLDevice*)_dev;
			kbyte* px = img->GetData();

			// 백업
			dev->_CommitTexture(0, this);
			glGetTexImage(_gl_type, mipno, pxfmt, pxtype, px);

			// 아마 작동은 하겠지만, 원천적으로 구조에 문제가 좀 있음
			if (mipno == 0)
			{
				int i, pitch = img->GetPitch();
				kbyte* p1 = k_new(pitch, kbyte);
				kbyte* p2 = px + (img->GetHeight() - 1) * pitch;

				for (i = 0; i < img->GetHeight(); i += 2)
				{
					memcpy(p1, px, pitch);
					memcpy(px, p2, pitch);
					memcpy(p2, p1, pitch);
					px += pitch;
					p2 -= pitch;
				}

				k_delete(p1);
			}

			// 리셋
			//kobj_unload(dev->_vprn.m_tex[0]);
			//dev->_vprn.m_tex[0] = NULL;
			//dev->_vprn.bd_tex[0] = true;
		}
	}

	// 리턴값
	map->data = img->GetData();
	map->pitch = img->GetPitch();
	map->slice = 0;

	map->width = img->GetWidth();
	map->height = img->GetHeight();
	map->depth = 0;

	// 잠금 저장
	_lock_mip = mipno;
	_lock_face = face;

	return true;
}

//
bool GLTexture::LeaveMap()
{
	k_return_value_if_fail(_lock_mip >= 0, false);

	// 그냥? 아님 밉맵?
	qgImage* img = _img_mip ? _img_mip : _img_rsw;
	k_return_value_if_fail(img, false);

	// 올림
	_UploadTexture(false, _lock_mip, (GLDevice*)_dev);

	// 지우라면 지우지요
	if (_img_mip)
	{
		_img_mip->Unload();
		_img_mip = NULL;
	}
	else if (!K_OFLAG(_flags, QGTEXF_IMAGE))
	{
		_img_rsw->Unload();
		_img_rsw = NULL;
	}

	// 정리
	_lock_mip = -1;
	_lock_face = QGTXFX_NONE;

	return true;
}


//////////////////////////////////////////////////////////////////////////
// 모션 텍스쳐
KIMPL_SUBCLASS(GLTextureMotion, "GLTextureMotion");

//
GLTextureMotion::GLTextureMotion() throw()
{
}

//
GLTextureMotion::~GLTextureMotion() throw()
{
	// 렌더러 마다 텍스쳐 해제가 다르므로 여기서 함
	if (_mt_preimgs.IsEmpty())
		CleanStreams();
	else
	{
#if _SB_64_
		kForEach(kuintptr n, _mt_preimgs)
		{
			GLuint tex = (GLuint)n;
			glDeleteTextures(1, &tex);
		}
#else
		// Uint가 아니고 uintptr이라 이거 못씀. 32비트에선 되겠네.. 해보까?
		static_assert(sizeof(kuintptr) == sizeof(GLuint), "Texture storage size problem. try upper code.");
		glDeleteTextures(_mt_count, _mt_preimgs.Data());
#endif
	}
}

//
qgITextureMotion* GLTextureMotion::GetMotion()
{
	return this;
}

//
bool GLTextureMotion::MotionUpdate(float adv)
{
	k_return_value_if_fail(_lock_mip < 0, false);

	//
	kuintptr ptr;
	bool res;

	if (!ManualUpdate(adv, res, ptr))
		return false;

	if (res)
	{
		GLDevice* dev = (GLDevice*)_dev;
		dev->_CommitTexture(0, this);

		// 실제 텍스쳐 사이즈랑 다르면 다르게 처리해야함
		// 반드시 _gl_type==GL_TEXTURE_2D 임.
		qgImage* img;

		if (_img_rsw)
		{
			// 이미지를 늘려서 그린다
			// 우와! 이거 엄청 느리다!!!!
			img = _img_rsw;
			qgBlitImage::StrechBlit(QG_BLITOP_TEX, img, NULL, _mt_ucmimage, NULL, tpl::ColorValues::Transparent);
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img->GetWidth(), img->GetHeight(), _pixel_fmt, _pixel_type, img->GetData());
		}
		else
		{
			// 무조건 32비트 RGBA만 사용함
			img = _mt_ucmimage;
			glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img->GetWidth(), img->GetHeight(), _pixel_fmt, _pixel_type, img->GetData());
		}
	}
	else if (ptr != 0)
	{
		if (ptr != _node_desc)
		{
			// 헐... 참조가 엄꾸나
			_node_desc = ptr;
		}
	}

	return true;
}

//
bool GLTextureMotion::_CreateMotion(const char* filename, kint flags, GLDevice* dev)
{
	// 스트림
	kStream* stm = dev->FileOpen(filename, true);
	if (stm == NULL)
		return NULL;

	if (!qgFwSupp::CheckHeadAndTail(stm))
	{
		stm->Unload();
		return NULL;
	}

	// 모션
	if (!ParseMotion(stm, K_OFLAG(flags, QGTEXF_PRELOAD)))
		return false;

	// GL과의 호환성을 위해서는 RGBA -> BGRA를 쓰는 것이 좋다... 지만 먼저 GL부터 고쳐야함
	qgImage* img = _mt_ucmimage;

	if (_mt_preimgs.IsEmpty())
	{
		// 한장 갖다 잠궈 씀
		if (!_InitProperty(QGTEXTURE_2D, filename, img->GetWidth(), img->GetHeight(), 1, 1, QGTEXF_S2D | QGRESF_DYNAMIC | QGTEXF_MOTION, QGCF32_RGBA, dev) ||
			!_CreateTexture(NULL, dev))
			return false;

		// 실제 텍스쳐 사이즈랑 다르면 버퍼를 다시 만들어야함 -> 잠그기용
		if (K_OFLAG(_insts, QGTEXI_ADJPOW2 | QGTEXI_ADJSQR))
			_img_rsw = kobj_new(qgImage, _awidth, _aheight, _fmt);
	}
	else
	{
		// 모두 만들어 놓기
		if (!_InitProperty(QGTEXTURE_2D, filename, img->GetWidth(), img->GetHeight(), 1, 1, QGTEXF_S2D | QGTEXF_MOTION, QGCF32_RGBA, dev))
			return false;

		// 실제 텍스쳐 사이즈랑 다르면 버퍼를 다시 만들어야함 -> 잠그기용
		qgImage* imgexp;

		if (K_OFLAG(_insts, QGTEXI_ADJPOW2 | QGTEXI_ADJSQR) == 0)
			imgexp = NULL;
		else
		{
			_img_rsw = kobj_new(qgImage, _awidth, _aheight, _fmt);
			imgexp = _img_rsw;
		}

		//
		GLuint tex;

		for (kuint i = 0; i < _mt_count; i++)
		{
			if (!_CreateInternal(&tex, dev))
			{
				// 오류 난 프레임만 패스
				tex = 0;
			}
			else
			{
				// 이미지를 만들어 써 넣는다
				if (UpdateStreamImage(i))
				{
					if (imgexp)
					{
						// 이미지 늘려서 그림
						qgBlitImage::StrechBlit(QG_BLITOP_TEX, imgexp, NULL, img, NULL, tpl::ColorValues::Transparent);
						glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, imgexp->GetWidth(), imgexp->GetHeight(), _pixel_fmt, _pixel_type, imgexp->GetData());
					}
					else
					{
						// 무조건 32비트 RGBA만 사용함
						glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, img->GetWidth(), img->GetHeight(), _pixel_fmt, _pixel_type, img->GetData());
					}
				}
			}

			_mt_preimgs[i] = (kuintptr)tex;
		}

		// 0번 넣어 둠
		_node_desc = _mt_preimgs[0];

		// 정리
		CleanStreams();

		K_YFLAG(_insts, QGTEXI_LIST, true);
	}

	// 오케
	return true;
}


//////////////////////////////////////////////////////////////////////////
// 오프 텍스쳐
KIMPL_SUBCLASS(GLTextureFBO, "GLTextureFBO");

#define USE_PRINT_FBO			0

//
GLTextureFBO::GLTextureFBO() throw()
{
}

//
GLTextureFBO::~GLTextureFBO() throw()
{
	if (_fbo != 0)
		QglDeleteFramebuffers(1, &_fbo);

	if (_db)
		_db->Unload();
}

//
qgITextureOff* GLTextureFBO::GetOff()
{
	return this;
}

//
bool GLTextureFBO::_CreateOff(GLDevice* dev)
{
	GLuint fbo = 0;

	// 프레임버퍼
	QglGenFramebuffers(1, &fbo);
	if (fbo == 0)
		return false;

	QglBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glDrawBuffer(GL_COLOR_ATTACHMENT0);

	// 프레임버퍼에 텍스쳐 붙임
	dev->_CommitTexture(0, this);	// 아마도 여러번 바인드 될듯...

	switch (K_CAST_ENUM(_textype))
	{
		case QGTEXTURE_1D:
			QglFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_1D, (GLuint)_node_desc, 0);
			break;

		case QGTEXTURE_2D:
			QglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, (GLuint)_node_desc, 0);
			break;

		case QGTEXTURE_3D:		// 미구현
		case QGTEXTURE_CUBE:	// 미구현
		default:
			QglBindFramebuffer(GL_FRAMEBUFFER, 0);
			QglDeleteFramebuffers(1, &fbo);
			return false;
	}

	// 검사
	const char* chk = _CheckFBO();

	QglBindFramebuffer(GL_FRAMEBUFFER, 0);

	if (chk != NULL)
	{
		kobj_tracet(KLV_ERROR, KERR_INVAL, chk);

		QglDeleteFramebuffers(1, &fbo);

		return false;
	}

	// 오케
	_fbo = fbo;

	return true;
}

//
const char* GLTextureFBO::_CheckFBO()
{
	GLenum s = QglCheckFramebufferStatus(GL_FRAMEBUFFER);
	const char* msg;

	switch (s)
	{
		case GL_FRAMEBUFFER_COMPLETE:
			msg = NULL;
			break;

		case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
			msg = "invalid read buffer";
			break;

		case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
			msg = "invalid draw buffer";
			break;

		case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
			msg = "incomplete image attachments";
			break;

		case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
			msg = "different format found";
			break;

		case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
			msg = "different dimension found";
			break;

		case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
			msg = "missing image";
			break;

		case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
			msg = "invalid multisample";
			break;

		case GL_FRAMEBUFFER_UNSUPPORTED:
			msg = "unsupported format";
			break;

		default:
			msg = "unknown error";
			break;
	}

	return msg;
}

bool GLTextureFBO::EnterOff(kint clearance, const tpl::ColorValue& color)
{
	if (_lock_off)
		return false;

	// 깊이 버퍼
	if (_db == NULL)
	{
		_db = (GLDepthBuffer*)_dev->CreateDepthBuffer(_awidth, _aheight);

		if (_db != NULL && !_db->Attach(this))
		{
			_db->Unload();
			_db = NULL;
		}
	}
	else
	{
		if (_db->GetLastAttach() != this && !_db->Attach(this))
		{
			_db->Unload();
			_db = NULL;
		}
	}

	// 설정
	GLDevice* dev = (GLDevice*)_dev;
	dev->SetTexture(0, NULL);
	dev->_CommitTexture(0, NULL);
	dev->_SetRenderTarget(this);	// 뷰포트 바꿈

	// 바인드
	QglBindFramebuffer(GL_FRAMEBUFFER, _fbo);
	glDrawBuffer(GL_COLOR_ATTACHMENT0);
	dev->_ClearDevice(clearance, tpl::Color(color), 0, 1.0f);

	// 올ㅋ
	_lock_off = true;

	return true;
}

bool GLTextureFBO::LeaveOff()
{
	if (!_lock_off)
		return false;

	//
	QglBindFramebuffer(GL_FRAMEBUFFER, 0);

	//
	GLDevice* dev = (GLDevice*)_dev;
	dev->_SetRenderTarget(NULL);
	dev->SetTexture(0, NULL);

	//
	_lock_off = false;

	return true;
}


//////////////////////////////////////////////////////////////////////////
// 뎁스 버퍼
KIMPL_SUBCLASS(GLDepthBuffer, "GLDepthBuffer");

#define USE_RENDERBUFFER_DEPTH	1

//
GLDepthBuffer::GLDepthBuffer() throw()
{
}

//
GLDepthBuffer::~GLDepthBuffer() throw()
{
	if (_node_desc != 0)
	{
		GLuint n = (GLuint)_node_desc;
#if USE_RENDERBUFFER_DEPTH
		QglDeleteRenderbuffers(1, &n);
#else
		glDeleteTextures(1, &n);
#endif
	}
}

//
bool GLDepthBuffer::_Create(kuint width, kuint height, GLDevice* dev)
{
	bool usestencil = dev->GetCaps().supp_stencil;
	GLuint dsb;

#if USE_RENDERBUFFER_DEPTH
	// 렌더 버퍼 사용
	QglGenRenderbuffers(1, &dsb);
	if (dsb == 0)
		return false;

	QglBindRenderbuffer(GL_RENDERBUFFER, dsb);

	// 프레임버퍼에 깊이 렌더 버퍼 붙임
#ifdef GL_EXT_packed_depth_stencil
	if (usestencil && TestGLCS(QGLX_EXT_PACKED_DEPTH_STENCIL))
	{
		// EXT_PACKED_DEPTH_STENCIL은 버전 3.0 이상만 지원함
		QglRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
		_bind = BindDepthAndStencil;
	}
	else
#endif
	{
		// 3.0 미만은 스텐실 포기해야함... 인데 될때도 있더라!!!
		if (usestencil)
		{
			QglRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_STENCIL, width, height);
			// 다만 3.0이하는 GL_DEPTH_STENCIL_ATTACHMENT 가 안된다는거
			_bind = BindDepthWithStencil;
		}
		else
		{
			// 스텐실 포기
			QglRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
			_bind = BindDepthOnly;
		}
	}
#else
	// 텍스쳐 사용
	dev->_CommitTexture(0, NULL);
	glEnable(GL_TEXTURE_2D);

	glGenTextures(1, &dsb);
	glBindTexture(GL_TEXTURE_2D, dsb);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	// 프레임버퍼에 깊이 렌더 버퍼 붙임
#ifdef GL_EXT_packed_depth_stencil
	if (usestencil && TestGLCS(QGLX_EXT_PACKED_DEPTH_STENCIL))
	{
		// EXT_PACKED_DEPTH_STENCIL은 버전 3.0 이상만 지원함
		glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width, height, 0,
			GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
		_bind = BindDepthAndStencil;
	}
	else
#endif
	{
		// 3.0 미만은 스텐실 포기해야함... 인데 될때도 있더라!!!
		if (usestencil)
		{
			glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width, height, 0,
				GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);

			if (glGetError() == GL_NO_ERROR)
			{
				// 스텐실 됨
				_bind = BindDepthWithStencil;
			}
			else
			{
				// 스텐실 포기
				glTexImage2D(GL_TEXTURE_2D, 0, GLConvert::DepthComponent(dev->GetCaps().val_depth_bpp),
					width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
				_bind = BindDepthOnly;
			}
		}
		else
		{
			// 스텐실 포기
			glTexImage2D(GL_TEXTURE_2D, 0, GLConvert::DepthComponent(dev->GetCaps().val_depth_bpp),
				width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
			_bind = BindDepthOnly;
		}
	}

	glDisable(GL_TEXTURE_2D);
#endif

	//
	_node_desc = dsb;
	_width = width;
	_height = height;

	return true;
}

//
bool GLDepthBuffer::Attach(GLTextureFBO* texfbo)
{
	k_return_value_if_fail(texfbo, false);

	GLuint fbo = (GLuint)texfbo->GetDescription();
	GLuint dsb = (GLuint)_node_desc;

	QglBindFramebuffer(GL_FRAMEBUFFER, fbo);
	glDrawBuffer(GL_COLOR_ATTACHMENT0);

#if USE_RENDERBUFFER_DEPTH
	switch (K_CAST_ENUM(_bind))
	{
		case BindDepthAndStencil:
			QglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, dsb);
			break;

		case BindDepthWithStencil:
			QglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, dsb);
			QglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, dsb);
			break;

		default:
			QglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, dsb);
			break;
	}
#else
	switch (K_CAST_ENUM(_bind))
	{
		case BindDepthAndStencil:
			QglFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, dsb, 0);
			break;

		case BindDepthWithStencil:
			QglFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dsb, 0);
			QglFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, dsb, 0);
			break;

		default:
			QglFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dsb, 0);
			break;
	}
#endif

	//
	const char* chk = GLTextureFBO::_CheckFBO();

	QglBindFramebuffer(GL_FRAMEBUFFER, 0);

	if (chk != NULL)
	{
		kobj_tracet(KLV_ERROR, KERR_INVAL, chk);

		return false;
	}

	//
	_lastfbo = texfbo;	// 참조 안함...

	return true;
}

#endif	// _QG_USE_GL

